// 
//  GFWInterceptorLibrary.mm
//  GFWInterceptor
//  
//  Created by overboming on 2010-06-24.
//  Copyright 2010 ForwardSlashStudio. All rights reserved.
// 


#import "GFWInterceptorLibrary.h"
#import <CFNetwork/CFNetwork.h>
#import "NSData+Base64.h"

NSString *forwardURL(NSURL *theURL){

    if(theURL == nil) return nil;
    NSURL *newURL = theURL;
    NSString *urlstring = [newURL host];
    #ifdef DEBUG
    NSLog(@"host string %@", urlstring);
    #endif
    
    NSString *schemestr = [newURL scheme];
    if([schemestr rangeOfString:@"http" options:NSCaseInsensitiveSearch].location == NSNotFound)
        return nil;
    if([[theURL absoluteString] isEqualToString:@"CFNETWORK_BOGUS_URL"]){  //this is something weird :>
        return nil;
    }
    
    for (NSString *keyword in keywordlist)
    {
      if((keyword != nil && [urlstring rangeOfString:keyword options:NSCaseInsensitiveSearch].location != NSNotFound) || forwordallmode){          
          
          NSString *firsturl= [[[theURL absoluteString] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
          NSString *base64url = [[firsturl dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
          
          
          #ifdef DEBUG
          NSLog(@"Matching Keyword is %@ URL to decode as base64 %@",keyword,base64url);
          NSLog(@"username: %@, password %@", username, password);
          #endif
          
          return [NSString stringWithFormat:@"%@%@?url=%@&e=%d&u=%@&p=%@",
                       apiproxy,
                       [[newURL path] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
                       base64url,
                       enablecookie ? 1 : 0,
                       username,
                       password
                   ];  //forware to a whole new world         
      } 
    }
    return nil;
}

NSString *forwardURL_AP(NSURL *theURL){
    NSURL *newURL = theURL;
    NSString *urlstring = [newURL host];
    NSString *schemestr = [newURL scheme];
    if([schemestr rangeOfString:@"http" options:NSCaseInsensitiveSearch].location == NSNotFound)
        return nil;
    
    if(ignorehttps && [schemestr isEqualToString:@"https"])
      return nil; //let https go, else we take it seriously          
    
    NSLog(@"url %@",[newURL absoluteString]);
    
    if([apengine containsURL:[newURL absoluteString]]){
        NSString *firsturl= [[[theURL absoluteString] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
        NSString *base64url = [[firsturl dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
        #ifdef DEBUG
        NSLog(@"URL to decode as base64 %@", base64url);
        #endif
        return [NSString stringWithFormat:@"%@%@?url=%@",
                   apiproxy,
                   [newURL path],
                   base64url
               ];  //forware to a whole new world         
    }
    return nil;
}

#ifdef NEW_FEATURE
static IMP original_UIApplicationDelegate_applicationWillEnterForeground_;
id replaced_UIApplicationDelegate_applicationWillEnterForeground_(id self, SEL _cmd, UIApplication *application){
	NSLog(@"gfwintercpetor, applicationWillEnterForeground");
	original_UIApplicationDelegate_applicationWillEnterForeground_(self, _cmd, application);
}


static IMP original_UIApplicationDelegate__loadMainNibFile_;
id replaced_UIApplicationDelegate__loadMainNibFile_(id self, SEL _cmd){
	NSLog(@"gfwinterceptor, loadMainNibFile");
	//basic
	id delegate = [self delegate];
    Class $AppDelegate = delegate ? [delegate class] : [self class];    
	if ([delegate respondsToSelector:@selector(applicationWillEnterForeground:)]){
        MSHookMessageEx($AppDelegate, @selector(applicationWillEnterForeground:), (IMP)replaced_UIApplicationDelegate_applicationWillEnterForeground_, (IMP *)&original_UIApplicationDelegate_applicationWillEnterForeground_); 
	}
	original_UIApplicationDelegate__loadMainNibFile_(self, _cmd);
}
#endif


//here we use MSHookFunction to hook to CFNetwork Methods
static CFHTTPMessageRef (*original_CFHTTPMessageCreateRequest)(CFAllocatorRef alloc, CFStringRef requestMethod, CFURLRef url, CFStringRef httpVersion);  

CFHTTPMessageRef replaced_CFHTTPMessageCreateRequest(CFAllocatorRef alloc, CFStringRef requestMethod, CFURLRef url, CFStringRef httpVersion){
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    if(appspothttp){
        NSString *schemestr = [(NSURL *)url scheme];
        NSString *urlstring = [(NSURL *)url host];
        if([urlstring rangeOfString:@"appspot"].location != NSNotFound && [schemestr isEqualToString:@"https"]){
            NSRange schemerange;
            schemerange.location = 0;
            schemerange.length = 5;
            NSString *httpurlstr = [[(NSURL *)url absoluteString] stringByReplacingOccurrencesOfString:@"https" withString:@"http" 
                                                                        options: NSCaseInsensitiveSearch range:schemerange];
            url = (CFURLRef)[NSURL URLWithString:httpurlstr];
        }
    }
    
    NSString *newstr = nil;
    
    #ifdef DEBUG
    double start =  [[NSDate date] timeIntervalSince1970];
    #endif
    
    if(autoproxy)
        newstr = forwardURL_AP((NSURL *)url);
    else 
        newstr = forwardURL((NSURL *)url);
    
    #ifdef DEBUG
    double delta = [[NSDate date] timeIntervalSince1970] - start;
    NSLog(@"it takes %f ms to finish a url match",delta);
    #endif
    
    CFHTTPMessageRef result;
    if(newstr == nil){
        result = original_CFHTTPMessageCreateRequest(alloc,requestMethod,url,httpVersion);
        [pool release];
        return result;
    }
    else{
        CFURLRef newurl = CFURLCreateWithString(kCFAllocatorDefault,(CFStringRef)newstr,NULL);
        #ifdef DEBUG
        NSLog(@"newstr looks good! %@ new url? %@", newstr,[(NSURL *)newurl absoluteString]);
        #endif
        CFHTTPMessageRef originalmessage = original_CFHTTPMessageCreateRequest(alloc,requestMethod,newurl,httpVersion);
        [pool release];
        return originalmessage;   
    }
}

#if 0
static CFDataRef (*original_CFHTTPMessageCopySerializedMessage)(CFHTTPMessageRef request);

CFDataRef replaced_CFHTTPMessageCopySerializedMessage(CFHTTPMessageRef request){
    NSLog(@"------------captured request %@",CFHTTPMessageCopyRequestURL(request));
    return original_CFHTTPMessageCopySerializedMessage(request);
}

static CFURLRef (*original_CFHTTPMessageCopyRequestURL)(CFHTTPMessageRef request);


CFHTTPMessageRef myCFHTTPMessageCreateRequest(CFAllocatorRef alloc, CFStringRef requestMethod, CFURLRef url, CFStringRef httpVersion){
    //save a copy of old url
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
    if(appspothttp){
        NSString *schemestr = [(NSURL *)url scheme];
        NSString *urlstring = [(NSURL *)url host];
        if([urlstring rangeOfString:@"appspot"].location != NSNotFound && [schemestr isEqualToString:@"https"]){
            NSRange schemerange;
            schemerange.location = 0;
            schemerange.length = 5;
            NSString *httpurlstr = [[(NSURL *)url absoluteString] stringByReplacingOccurrencesOfString:@"https" withString:@"http" 
                                                                        options: NSCaseInsensitiveSearch range:schemerange];
            url = (CFURLRef)[NSURL URLWithString:httpurlstr];
        }
    }
    
    NSString *originalString = [[NSString alloc] initWithString:[(NSURL *)url absoluteString]];
    NSString *newstr = nil;
    
    #ifdef DEBUG
    NSLog(@"CFNetwork We have intercepted this string: %@",originalString);
    double start =  [[NSDate date] timeIntervalSince1970];
    #endif
    
    if(autoproxy)
        newstr = forwardURL_AP((NSURL *)url);
    else 
        newstr = forwardURL((NSURL *)url);
    
    #ifdef DEBUG
    double delta = [[NSDate date] timeIntervalSince1970] - start;
    NSLog(@"it takes %f ms to finish a url match",delta);
    #endif
    
    CFHTTPMessageRef result;
    if(newstr == nil){
        result = CFHTTPMessageCreateRequest(alloc,requestMethod, url,httpVersion);
        [pool release];
        return result;
    }
    else{
        CFURLRef newurl = (CFURLRef)[[NSURL alloc] initWithString:newstr];
        CFHTTPMessageRef originalmessage = CFHTTPMessageCreateRequest(alloc,requestMethod,newurl,httpVersion);
        //set headers
        CFHTTPMessageSetHeaderFieldValue(originalmessage,(CFStringRef)@"zcreal-nsurl",(CFStringRef)originalString);
        #ifdef DEBUG
        NSLog(@"newstr looks good! %@", newstr);
        NSLog(@"header for zcreal-nsurl %@",CFHTTPMessageCopyHeaderFieldValue(originalmessage,(CFStringRef)@"zcreal-nsurl"));
        #endif
        
        [pool release];
        return originalmessage;
   
    }
}


CFURLRef replaced_CFHTTPMessageCopyRequestURL(CFHTTPMessageRef request){
    NSLog(@"------------captured request start");
    
    CFHTTPMessageRef mrequest = myCFHTTPMessageCreateRequest(kCFAllocatorDefault,
                                    CFHTTPMessageCopyRequestMethod(request),original_CFHTTPMessageCopyRequestURL(request),CFHTTPMessageCopyVersion(request));
    //need to send headers and body;
    CFHTTPMessageSetBody(mrequest,CFHTTPMessageCopyBody(request));
    NSDictionary *headers = (NSDictionary *)CFHTTPMessageCopyAllHeaderFields(request);
    NSEnumerator *enumerator = [headers keyEnumerator];
    id key;
    while ((key = [enumerator nextObject])) {
        //set mrequest with our key value;
        CFHTTPMessageSetHeaderFieldValue(mrequest,(CFStringRef)key,(CFStringRef)[headers valueForKey:key]);
    }
    NSLog(@"------------captured request end");
    return original_CFHTTPMessageCopyRequestURL(mrequest);
}


static CFReadStreamRef (*original_CFReadStreamCreateForHTTPRequest)(CFAllocatorRef alloc, CFHTTPMessageRef request);

CFReadStreamRef replaced_CFReadStreamCreateForHTTPRequest(CFAllocatorRef alloc, CFHTTPMessageRef request){
    //since url can't even be edited, we will create a new one with modified url
    NSLog(@"=======================holy shit we won! CFReadStreamCreateForHTTPRequest");
    
    CFHTTPMessageRef mrequest = replaced_CFHTTPMessageCreateRequest(kCFAllocatorDefault,
                                    CFHTTPMessageCopyRequestMethod(request),CFHTTPMessageCopyRequestURL(request),CFHTTPMessageCopyVersion(request));
    //need to send headers and body;
    CFHTTPMessageSetBody(mrequest,CFHTTPMessageCopyBody(request));
    NSDictionary *headers = (NSDictionary *)CFHTTPMessageCopyAllHeaderFields(request);
    NSEnumerator *enumerator = [headers keyEnumerator];
    id key;
    while ((key = [enumerator nextObject])) {
        //set mrequest with our key value;
        CFHTTPMessageSetHeaderFieldValue(mrequest,(CFStringRef)key,(CFStringRef)[headers valueForKey:key]);
    }
    return original_CFReadStreamCreateForHTTPRequest(alloc, mrequest);
}

static CFReadStreamRef (*original_CFReadStreamCreateForStreamedHTTPRequest)(CFAllocatorRef alloc, CFHTTPMessageRef requestHeaders, CFReadStreamRef requestBody);
CFReadStreamRef replaced_CFReadStreamCreateForStreamedHTTPRequest(CFAllocatorRef alloc, CFHTTPMessageRef requestHeaders, CFReadStreamRef requestBody){
    NSLog(@"*********************holy shit we won! CFReadStreamCreateForStreamedHTTPRequest");
    return original_CFReadStreamCreateForStreamedHTTPRequest(alloc, requestHeaders,requestBody);
}



#endif

NSMutableDictionary* buildPreferencePlist(){
    NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithCapacity:20];
    [prefs setObject:DEFAULT_URL forKey:@"apiproxy"];
    [prefs setObject:@"guest" forKey:@"username"];
    [prefs setObject:@"guest" forKey:@"password"];
    [prefs setObject:@"http://gfwinterceptor.googlecode.com" forKey:@"coolshit"];
    [prefs setObject:[NSNumber numberWithBool:NO] forKey:@"autoproxy"];
    [prefs setObject:[NSNumber numberWithBool:NO] forKey:@"ignorehttps"];
    [prefs setObject:[NSNumber numberWithBool:YES] forKey:@"isenabled"];
    [prefs setObject:[NSNumber numberWithBool:YES] forKey:@"enablecookie"];
    [prefs setObject:[NSNumber numberWithBool:NO] forKey:@"appspothttp"];
    [prefs setObject:[NSNumber numberWithBool:NO] forKey:@"rebuildplist"];
    [prefs setObject:@"twit;tweet;img.ly;foursquare;yfrog" forKey:@"keyword1"];
    [prefs setObject:@"facebook;fbcdn;" forKey:@"keyword2"];
    [prefs setObject:@"youtube;ytimg" forKey:@"keyword3"];
    [prefs setObject:@"dropbox;tumblr;hellotxt" forKey:@"keyword4"];
    [prefs writeToFile:@"/var/mobile/Library/Preferences/com.overboming.gfwinterceptor.plist" atomically:YES];   
    return prefs;
}

extern "C" void GFWInterceptorInitialize() {

    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];    
    #ifdef DEBUG
    double start =  [[NSDate date] timeIntervalSince1970];
    #endif

    NSString *identifier = [[NSBundle mainBundle] bundleIdentifier];
    #ifdef DEBUG
    NSLog(@"out side init: %@",identifier);
    #endif
    if(identifier == nil)
        identifier = @"shouldneverknow";

    if ((identifier != nil && [identifier rangeOfString:@".apple."].location == NSNotFound && [identifier rangeOfString:@"taobao"].location == NSNotFound && [identifier rangeOfString:@"locationd"].location == NSNotFound) || 
         [identifier rangeOfString:@"springboard"].location != NSNotFound ||
         [identifier rangeOfString:@"safari"].location != NSNotFound ||
         [identifier rangeOfString:@"youtube"].location != NSNotFound ||
         [identifier isEqualToString:@"shouldneverknow"]
        ) {
    
	    #ifdef DEBUG
        NSLog(@"init GFWInterceptor");
        #endif 
        
        NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.overboming.gfwinterceptor.plist"];
        if(!prefs ||
          ([prefs objectForKey:@"rebuildplist"] && [[prefs objectForKey:@"rebuildplist"] boolValue])){
            prefs = buildPreferencePlist();
            
            #ifdef DEBUG
            NSLog(@"Dictionary not found!, rebuild complete");
            #endif 
            
        } 
        
        BOOL enabled = [prefs objectForKey:@"isenabled"] ? [[prefs objectForKey:@"isenabled"] boolValue] : YES;
        
        if (!enabled){
    	    #ifdef DEBUG
            NSLog(@"not enabled, return");
            #endif 
            [pool release];
            return;
        }
        
        apiproxy = [prefs objectForKey:@"apiproxy"] ? [prefs objectForKey:@"apiproxy"] : DEFAULT_URL;
        autoproxy = [prefs objectForKey:@"autoproxy"] ? [[prefs objectForKey:@"autoproxy"] boolValue] : NO;
        appspothttp = [prefs objectForKey:@"appspothttp"] ? [[prefs objectForKey:@"appspothttp"] boolValue] : NO;
        enablecookie = [prefs objectForKey:@"enablecookie"] ? [[prefs objectForKey:@"enablecookie"] boolValue] : YES;

        serverlist = [NSArray arrayWithObjects:@"Please be aware built in server should be protected",
                                               nil];
                                               
        long seconds = (long)[[NSDate date] timeIntervalSince1970];
        int num = seconds % [serverlist count];
        
        if([apiproxy isEqualToString:DEFAULT_URL]){
            apiproxy = [NSString stringWithString:[serverlist objectAtIndex:num]];
            #ifdef DEBUG
            NSLog(@"choose server %@",apiproxy);
            #endif 
            
        }
        if(autoproxy)
            apengine = [APRules sharedInstance];
        
        ignorehttps = [prefs objectForKey:@"ignorehttps"] ? [[prefs objectForKey:@"ignorehttps"] boolValue] : NO;        
        appkeylist = [[NSMutableArray alloc] initWithCapacity:6];
        username = [[[[prefs objectForKey:@"username"] ? [prefs objectForKey:@"username"] : @"guest" dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString] retain];
        password = [[[[prefs objectForKey:@"password"] ? [prefs objectForKey:@"password"] : @"guest" dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString] retain];

        #ifdef DEBUG
        NSLog(@"username: %@, password %@", username, password);
        #endif


        //init app identifier keywords
        int i;
        for(i = 1; i <= NUM_OF_APP_KEYWORDS; i ++){
            NSString *keywordline = [prefs objectForKey:[NSString stringWithFormat:@"%@%d",@"appkey",i]];
            if(keywordline == nil || [keywordline length] <= 1)
                continue;
            NSString *trimmedString = [keywordline stringByTrimmingCharactersInSet:
                                              [NSCharacterSet whitespaceAndNewlineCharacterSet]];
           NSArray *linekeywords = [trimmedString componentsSeparatedByString:@";"];
           for(NSString *akeyword in linekeywords){
               if(akeyword != nil && [akeyword length] > 1){
                   [appkeylist addObject:akeyword];
                   #ifdef DEBUG
                   NSLog(@"appkey %@",akeyword);        
                   #endif       
               }
           }
        }
        keywordlist = [[NSMutableArray alloc] initWithCapacity:15];
        
        //check app key
        forwordallmode = NO;
        for(NSString *appkey in appkeylist){
            if([identifier rangeOfString:appkey options:NSCaseInsensitiveSearch].location != NSNotFound){
               #ifdef DEBUG
               NSLog(@"forward all mode %@", appkey);        
               #endif       
                forwordallmode = YES;
                [keywordlist addObject:@"youshouldneverknowthis"];
                break;
            }
        }
        
        
        //init domain keywords
        if(!forwordallmode){        
            for(i = 1; i <= NUM_OF_KEYWORDS; i ++){
                NSString *keywordline = [prefs objectForKey:[NSString stringWithFormat:@"%@%d",@"keyword",i]];
                if(keywordline == nil || [keywordline length] <= 1)
                    continue;
                NSString *trimmedString = [keywordline stringByTrimmingCharactersInSet:
                                                  [NSCharacterSet whitespaceAndNewlineCharacterSet]];
               NSArray *linekeywords = [trimmedString componentsSeparatedByString:@";"];
               for(NSString *akeyword in linekeywords){
                   if(akeyword != nil && [akeyword length] > 1){
                       [keywordlist addObject:akeyword];
                       #ifdef DEBUG
                       NSLog(@"keyword %@",akeyword);        
                       #endif       
                   }
               }
            }
        }
        
        MSHookFunction(CFHTTPMessageCreateRequest,replaced_CFHTTPMessageCreateRequest, &original_CFHTTPMessageCreateRequest);
#ifdef NEW_FEATURE

        Class $UIApplication = objc_getClass("UIApplication");
	    BOOL isFirmware3x_ = (class_getInstanceMethod($UIApplication, @selector(applicationState)) == NULL);
        if(isFirmware3x_ == NO){
            MSHookMessageEx([UIApplication class], @selector(_loadMainNibFile), (IMP)replaced_UIApplicationDelegate__loadMainNibFile_, (IMP *)&original_UIApplicationDelegate__loadMainNibFile_); 
        }
#endif        
        #if 0
        MSHookFunction(CFReadStreamCreateForHTTPRequest,replaced_CFReadStreamCreateForHTTPRequest,&original_CFReadStreamCreateForHTTPRequest);        
        MSHookFunction(CFReadStreamCreateForStreamedHTTPRequest,replaced_CFReadStreamCreateForStreamedHTTPRequest,&original_CFReadStreamCreateForStreamedHTTPRequest);                
        MSHookFunction(CFHTTPMessageCopySerializedMessage, replaced_CFHTTPMessageCopySerializedMessage, &original_CFHTTPMessageCopySerializedMessage);
        MSHookFunction(CFHTTPMessageCopyRequestURL, replaced_CFHTTPMessageCopyRequestURL,&original_CFHTTPMessageCopyRequestURL);        
        #endif
	}
	#ifdef DEBUG
    double interval = [[NSDate date] timeIntervalSince1970] - start;
    NSLog(@"gfwinterceptor init takes about %f seconds", interval);
	#endif
    [pool release];
}
